﻿package org.philipisapain.events
{
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.external.ExternalInterface;
	import flash.utils.Dictionary;
	import flash.utils.describeType;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	import mx.utils.DescribeTypeCache;
	import mx.utils.DescribeTypeCacheRecord;
	
	public class EventHub extends EventDispatcher
	{	
		protected static var COMPONENT_ID:String = ExternalInterface.objectID;
		
		protected static var _instance:EventHub;
				
		protected var _subscribedEvents:Object;
		
		public function EventHub():void 
		{
			init();
		}
		
		protected function init():void
		{
			ExternalInterface.addCallback("dispatchEvent", dispatchEventFromJS);
			_subscribedEvents = new Object();
		}
		
		protected function dispatchEventFromJS(event:Object):void
		{	
			var e:EventHubEvent = getEventFromObject(event);
			super.dispatchEvent(Event(e));
		}

		public function sendEventToJS(event:EventHubEvent):void
		{	
			var eventObj:Object = getObjectFromEvent(event);
			
			//Add the source because we're externalizing.
			eventObj.source = COMPONENT_ID;
			
			ExternalInterface.call("eventHub.dispatchEvent", eventObj);
		}
		
		public override function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
		{
			super.addEventListener(type, listener, useCapture, priority, useWeakReference);
			
			if (!_subscribedEvents[type]) //if we haven't already got some subscribers.....
			{
				_subscribedEvents[type] = new Dictionary(); //create a dictionary to store them
				_subscribedEvents[type][listener] = true; //store the listener function as a child of 'type'
				addSubscription(type); //tell the world we're a subscriber
			}
		}
		public override function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void
		{
			super.removeEventListener(type, listener, useCapture);
			if(_subscribedEvents[type][listener])
			{
				delete _subscribedEvents[type][listener]; //remove the listener from our list of subscribers
			
				var subscribers:Dictionary = _subscribedEvents[type] as Dictionary;
				var p:Function;
				for each(p in _subscribedEvents[type])
				{
					//If there are more listeners for this event, then just keep listening
					return;
				}
			
				//otherwise unsubscribe from the event with JS EventHub
				removeSubscription(type);
			}
		}

		public override function dispatchEvent(event:Event):Boolean
		{	
			var e:EventHubEvent = EventHubEvent(event);
			
			if(e)
			{
				if (e.externalize)
				{
					sendEventToJS(e); 
				}
			}
			else
			{
				//TODO: Shouldn't we just allow all events to come through here?
				throw new Error("EventHub: Trying to dispatch non-EventHubEvent Event");
			}
			
			return super.dispatchEvent(event);
		}
		
		private function addSubscription(eventType:String):void
		{
			ExternalInterface.call("eventHub.addSubscription", eventType, COMPONENT_ID);
		}
		private function removeSubscription(eventType:String):void
		{
			ExternalInterface.call("eventHub.removeSubscription", eventType, COMPONENT_ID);
		}
		
		public static function get instance():EventHub
		{
			if (_instance == null)
			{
				_instance = new EventHub();
			}
			return _instance;
		}
		
		protected function getEventFromObject(eventObj:Object):EventHubEvent
		{
			var eventParts:Array = String(eventObj.type).split("::");
			var classPath:String = eventParts[0];
			var eventType:String = eventParts[1];
			var eventClass:Class = getDefinitionByName(classPath) as Class;
			var event:EventHubEvent = new eventClass(eventObj.type, eventObj.bubbles, eventObj.cancelable, false);
			
			var record:DescribeTypeCacheRecord = DescribeTypeCache.describeType(event);
			
			var accessors:XMLList = record.typeDescription.accessor;
			var accessor:XML;
			var prop:String;
			for each(accessor in accessors)
			{
				if(accessor.@access != "readonly")
				{
					prop = accessor.@name;
					event[prop] = eventObj[prop];
				}
			}
			
			return event;
		}
		
		protected function getObjectFromEvent(event:EventHubEvent):Object
		{	
			var eventObj:Object = {};
			
			var record:DescribeTypeCacheRecord = DescribeTypeCache.describeType(event);
			
			var accessors:XMLList = record.typeDescription.accessor;
			var accessor:XML;
			var prop:String;
			
			for each(accessor in accessors)
			{
				prop = accessor.@name;
				eventObj[prop] = event[prop];
			}
			
			return eventObj;
		}
	}
}